from datetime import time, datetime
import traceback

from vnpy_ctastrategy.backtesting import BacktestingEngine

from vnpy_ctastrategy import (
    CtaTemplate,
    StopOrder,
    TickData,
    BarData,
    TradeData,
    OrderData,
    BarGenerator,
    ArrayManager,
)


class DualThrustStrategy(CtaTemplate):
    """"""

    author = "用Python的交易员"

    fixed_size = 1
    k1 = 0.4
    k2 = 0.6

    bars = []

    day_open = 0
    day_high = 0
    day_low = 0

    day_range = 0
    long_entry = 0
    short_entry = 0
    exit_time = time(hour=14, minute=55)

    long_entered = False
    short_entered = False

    parameters = ["k1", "k2", "fixed_size"]
    variables = ["day_open", "day_high", "day_low", "day_range", "long_entry", "short_entry"]

    def __init__(self, cta_engine, strategy_name, vt_symbol, setting):
        """"""
        super().__init__(cta_engine, strategy_name, vt_symbol, setting)

        self.bg = BarGenerator(self.on_bar)
        self.am = ArrayManager()
        self.bars = []
        self.is_back_test = isinstance(cta_engine, BacktestingEngine)

    def on_init(self):
        """
        Callback when strategy is inited.
        """
        self.write_log("策略初始化")
        self.load_bar(10)
        self.long_entered = False
        self.short_entered = False

    def on_start(self):
        """
        Callback when strategy is started.
        """
        self.write_log("策略启动")

    def on_stop(self):
        """
        Callback when strategy is stopped.
        """
        self.write_log("策略停止")

    def on_tick(self, tick: TickData):
        """
        Callback of new tick data update.
        """
        self.bg.update_tick(tick)

    def on_bar(self, bar: BarData):
        """
        Callback of new bar data update.
        """
        if not self.is_back_test:
            if bar.gateway_name == 'CTP':
                print(f"【ShortOnlyStrategy】{bar}")
        self.cancel_all()

        self.bars.append(bar)

        # 如果缓存的k线的数目小于两条，直接跳过
        if len(self.bars) <= 2:
            return
        else:
            # 把旧的K线推出一条
            self.bars.pop(0)
        # 取出上一条K线
        last_bar = self.bars[-2]

        # 如果上一条K线的日期不等于最新的K线的日期，已经进入新的一天了
        if last_bar.datetime.date() != bar.datetime.date():
            self.day_open = bar.open_price
            # 如果day_high存在且不为零
            if self.day_high:
                # 价格波动范围是前一天最高价-前一天最低价
                self.day_range = self.day_high - self.day_low
                # 做多的入场价格=K线开盘价 + k1*day_range
                self.long_entry = self.day_open + self.k1 * self.day_range
                # 做空的入场价格=K线开盘价-k2*day_range
                self.short_entry = self.day_open - self.k2 * self.day_range
            self.day_high = bar.high_price
            self.day_low = bar.low_price

            self.long_entered = False
            self.short_entered = False
        else:
            # 如果依旧在当天，通过最新的bar和缓存的数据比对来更新day_high和day_low
            self.day_high = max(self.day_high, bar.high_price)
            self.day_low = min(self.day_low, bar.low_price)

        # 判断异常 如果day_range不存在或者等于零 直接return
        if not self.day_range:
            return

        # 在收盘前的一段时间（这里设定的是下午14点55分）
        if bar.datetime.time() < self.exit_time:
            # 如果没有持仓
            if self.pos == 0:
                # 如果当前K线的收盘价大于当天的开盘价
                if bar.close_price > self.day_open:
                    # 如果多头还没入场过
                    if not self.long_entered:
                        # 按照long_entry的价格下一个fixed_size手的买多停止单
                        self.buy(self.long_entry, self.fixed_size, stop=True)
                else:
                    # 如果当前K线的收盘价格小于当天的开盘价格，并且如果空头还没有入场过
                    if not self.short_entered:
                        # 按照short_entry的价格下一个fixed_size手的卖空停止单
                        self.short(self.short_entry,
                                   self.fixed_size, stop=True)

            # 如果已经持有多头仓位
            elif self.pos > 0:
                self.long_entered = True
                # 按照short_entry的价格下一个fixed_size手的卖出停止单 （止损用途）
                self.sell(self.short_entry, self.fixed_size, stop=True)

                # 如果空头还没有入场过
                if not self.short_entered:
                    # 按照short_entry的价格下一个fixed_size手的卖空停止单
                    self.short(self.short_entry, self.fixed_size, stop=True)

            # 如果已经持有空头仓位
            elif self.pos < 0:
                self.short_entered = True

                # 按照long_entry的价格下一个fixed_size手的平空停止单 （止损用途）
                self.cover(self.long_entry, self.fixed_size, stop=True)

                # 如果多头没有入场过
                if not self.long_entered:
                    # 按照long_entry的价格下一个fixed_size手的买入停止单
                    self.buy(self.long_entry, self.fixed_size, stop=True)

        # 如果快收盘了
        else:
            if self.pos > 0:
                # 如果持有多头仓位，直接按照最新k线收盘价*0.99的价格卖掉所有仓位
                self.sell(bar.close_price * 0.99, abs(self.pos))
            elif self.pos < 0:
                # 如果持有空头仓位，直接按照最新k线收盘价*1.01的价格平空所有仓位
                self.cover(bar.close_price * 1.01, abs(self.pos))

        self.put_event()

    def on_order(self, order: OrderData):
        """
        Callback of new order data update.
        """
        pass

    def on_trade(self, trade: TradeData):
        """
        Callback of new trade data update.
        """
        try:
            self.push_msg(f"交易一笔成功，direction={trade.direction}，offset{trade.offset},price={trade.price}")
        except AttributeError:
            pass
        except Exception:
            msg: str = f"触发异常已停止\n{traceback.format_exc()}"
            self.write_log(msg)
            print(f"推送消息失败:{msg}")

        self.put_event()

    def on_stop_order(self, stop_order: StopOrder):
        """
        Callback of stop order update.
        """
        pass
